/**
 * Copyright 2018 orivil.com. All rights reserved.
 * Use of this source code is governed by a MIT-style
 * license that can be found at https://mit-license.org.
 */

package param

import (
	"encoding"
	"errors"
	"fmt"
	"mime/multipart"
	"net/url"
	"reflect"
	"regexp"
	"strconv"
	"strings"
	"unsafe"
	"time"
)

// ValidatorProvider 接口用于自定义 Validator
//type ValidatorProvider interface {
//	GetValidator() *Validator
//}

// FileHandler 定义上传文件处理函数, field 是上传文件的字段名, 如果一个字段上传了多个文件,
// 则该函数会被调用多次, 每次调用对应一个文件
type FileHandler func(field string, header *multipart.FileHeader) error

type fieldSetter func(begin uintptr, form multipart.Form) (msg, id string, err error)

type storage func(begin uintptr, form multipart.Form) (err error)

type Parser struct {
	Name     string
	setters  []fieldSetter
	storages []storage
}

// NewParser 新建解析器及验证器, 如果验证标签语法错误, 会发生 panic
func NewParser(schema interface{}, pvd *FieldNameProvider, validator *Validator) *Parser {
	if pvd == nil {
		pvd = NewFieldNameProvider(nil)
	}
	t := ReflectTypeOf(schema, pvd, validator)
	name := reflect.TypeOf(schema).String()
	vs := &Parser{Name: name}
	t.Range(func(kind reflect.Kind, field reflect.StructField, cdtPvd ConditionProvider) {
		param := field.Name
		offset := field.Offset
		var setter fieldSetter
		cdt := cdtPvd.(*condition)
		switch kind {
		case String:
			setter = func(begin uintptr, form multipart.Form) (msg, id string, err error) {
				value := url.Values(form.Value).Get(param)
				*(*string)(unsafe.Pointer(begin + offset)) = value
				if cdt != nil {
					msg, id = cdt.validStr(param, value)
					if msg != "" {
						return
					}
					msg, id = cdt.validEnum(param, form)
				}
				return
			}
		case Int:
			setter = func(begin uintptr, form multipart.Form) (msg, id string, err error) {
				var value int
				valueStr := url.Values(form.Value).Get(param)
				if valueStr != "" {
					value, err = strconv.Atoi(valueStr)
					if err != nil {
						return
					}
				}
				*(*int)(unsafe.Pointer(begin + offset)) = value
				if cdt != nil {
					msg, id = cdt.validNum(param, valueStr, float64(value))
					if msg != "" {
						return
					}
					msg, id = cdt.validEnum(param, form)
				}
				return
			}
		case Int32:
			setter = func(begin uintptr, form multipart.Form) (msg, id string, err error) {
				var value int64
				valueStr := url.Values(form.Value).Get(param)
				if valueStr != "" {
					value, err = strconv.ParseInt(valueStr, 10, 32)
					if err != nil {
						return
					}
				}
				*(*int32)(unsafe.Pointer(begin + offset)) = int32(value)
				if cdt != nil {
					msg, id = cdt.validNum(param, valueStr, float64(value))
					if msg != "" {
						return
					}
					msg, id = cdt.validEnum(param, form)
				}
				return
			}
		case Int64:
			setter = func(begin uintptr, form multipart.Form) (msg, id string, err error) {
				var value int64
				valueStr := url.Values(form.Value).Get(param)
				if valueStr != "" {
					value, err = strconv.ParseInt(valueStr, 10, 64)
					if err != nil {
						return
					}
				}
				*(*int64)(unsafe.Pointer(begin + offset)) = value
				if cdt != nil {
					msg, id = cdt.validNum(param, valueStr, float64(value))
					if msg != "" {
						return
					}
					msg, id = cdt.validEnum(param, form)
				}
				return
			}
		case Float32:
			setter = func(begin uintptr, form multipart.Form) (msg, id string, err error) {
				var value float64
				valueStr := url.Values(form.Value).Get(param)
				if valueStr != "" {
					value, err = strconv.ParseFloat(valueStr, 32)
					if err != nil {
						return
					}
				}
				if cdt != nil {
					msg, id = cdt.validNum(param, valueStr, value)
					if msg != "" {
						return
					}
					msg, id = cdt.validEnum(param, form)
				}
				*(*float32)(unsafe.Pointer(begin + offset)) = float32(value)
				return
			}
		case Float64:
			setter = func(begin uintptr, form multipart.Form) (msg, id string, err error) {
				var value float64
				valueStr := url.Values(form.Value).Get(param)
				if valueStr != "" {
					value, err = strconv.ParseFloat(valueStr, 64)
					if err != nil {
						return
					}
				}
				*(*float64)(unsafe.Pointer(begin + offset)) = value
				if cdt != nil {
					msg, id = cdt.validNum(param, valueStr, value)
					if msg != "" {
						return
					}
					msg, id = cdt.validEnum(param, form)
				}
				return
			}
		case Bool:
			setter = func(begin uintptr, form multipart.Form) (msg, id string, err error) {
				valueStr := url.Values(form.Value).Get(param)
				value := valueStr != ""
				*(*bool)(unsafe.Pointer(begin + offset)) = value
				return
			}
		case File:
			setter = func(begin uintptr, form multipart.Form) (msg, id string, err error) {
				if cdt != nil {
					msg, id = cdt.validFile(param, form)
				}
				return
			}
			vs.storages = append(vs.storages, func(begin uintptr, form multipart.Form) (err error) {
				handler := *(*FileHandler)(unsafe.Pointer(begin + offset))
				if handler == nil {
					return fmt.Errorf("field %s.%s: file handler is nil", name, param)
				}
				for _, header := range form.File[param] {
					err = handler(param, header)
					if err != nil {
						return err
					}
				}
				return nil
			})
		case TextUnmarshaler:
			setter = func(begin uintptr, form multipart.Form) (msg, id string, err error) {
				if cdt != nil {
					msg, id = cdt.validTextUnmarshaler(param, form)
					if msg != "" {
						return
					}
				}

				unmarshaler := *((*encoding.TextUnmarshaler)(unsafe.Pointer(begin + offset)))
				if unmarshaler == nil {
					unmarshaler = &time.Time{}
				}
				text := []byte(url.Values(form.Value).Get(param))
				if len(text) > 0 {
					err = unmarshaler.UnmarshalText(text)
				}
				return
			}
		case SliceString:
			setter = func(begin uintptr, form multipart.Form) (msg, id string, err error) {
				// TODO: 同下
				values := form.Value[param]
				if len(values) == 1 {
					vs := strings.Split(values[0], ",")
					values = vs
				}
				*(*[]string)(unsafe.Pointer(begin + offset)) = values
				if cdt != nil {
					msg, id = cdt.validItem(param, form)
					if msg != "" {
						return
					}
					msg, id = cdt.validEnum(param, form)
				}
				return
			}
		case SliceInt:
			setter = func(begin uintptr, form multipart.Form) (msg, id string, err error) {
				values := form.Value[param]
				// TODO: swagger 在传递数组参数时直接用 "," 包装成一个参数如:  -d "BookID=1&Tags=1,2"
				// 实际应该是 -d "BookID=1&Tags=1&Tags=2"
				if len(values) == 1 {
					vs := strings.Split(values[0], ",")
					values = vs
				}
				var ints = make([]int, len(values))
				for idx, value := range values {
					ints[idx], err = strconv.Atoi(value)
					if err != nil {
						return
					}
				}
				*(*[]int)(unsafe.Pointer(begin + offset)) = ints
				if cdt != nil {
					msg, id = cdt.validItem(param, form)
					if msg != "" {
						return
					}
					msg, id = cdt.validEnum(param, form)
				}
				return
			}
		case SliceInt32:
			setter = func(begin uintptr, form multipart.Form) (msg, id string, err error) {
				values := form.Value[param]
				if len(values) == 1 {
					vs := strings.Split(values[0], ",")
					values = vs
				}
				// TODO: 同上
				var ints = make([]int32, len(values))
				for idx, value := range values {
					i32, e := strconv.ParseInt(value, 10, 32)
					if e != nil {
						err = e
						return
					}
					ints[idx] = int32(i32)
				}
				*(*[]int32)(unsafe.Pointer(begin + offset)) = ints
				if cdt != nil {
					msg, id = cdt.validItem(param, form)
					if msg != "" {
						return
					}
					msg, id = cdt.validEnum(param, form)
				}
				return
			}
		case SliceInt64:
			setter = func(begin uintptr, form multipart.Form) (msg, id string, err error) {
				// TODO: 同上
				values := form.Value[param]
				if len(values) == 1 {
					vs := strings.Split(values[0], ",")
					values = vs
				}
				var ints = make([]int64, len(values))
				for idx, value := range values {
					i64, e := strconv.ParseInt(value, 10, 64)
					if e != nil {
						err = e
						return
					}
					ints[idx] = int64(i64)
				}
				*(*[]int64)(unsafe.Pointer(begin + offset)) = ints
				if cdt != nil {
					msg, id = cdt.validItem(param, form)
					if msg != "" {
						return
					}
					msg, id = cdt.validEnum(param, form)
				}
				return
			}
		case SliceFloat32:
			setter = func(begin uintptr, form multipart.Form) (msg, id string, err error) {
				// TODO: 同上
				values := form.Value[param]
				if len(values) == 1 {
					vs := strings.Split(values[0], ",")
					values = vs
				}
				var floats = make([]float32, len(values))
				for idx, value := range values {
					f32, e := strconv.ParseFloat(value, 32)
					if e != nil {
						err = e
						return
					}
					floats[idx] = float32(f32)
				}
				*(*[]float32)(unsafe.Pointer(begin + offset)) = floats
				if cdt != nil {
					msg, id = cdt.validItem(param, form)
					if msg != "" {
						return
					}
					msg, id = cdt.validEnum(param, form)
				}
				return
			}
		case SliceFloat64:
			setter = func(begin uintptr, form multipart.Form) (msg, id string, err error) {
				// TODO: 同上
				values := form.Value[param]
				if len(values) == 1 {
					vs := strings.Split(values[0], ",")
					values = vs
				}
				var floats = make([]float64, len(values))
				for idx, value := range values {
					f64, e := strconv.ParseFloat(value, 64)
					if e != nil {
						err = e
						return
					}
					floats[idx] = f64
				}
				*(*[]float64)(unsafe.Pointer(begin + offset)) = floats
				if cdt != nil {
					msg, id = cdt.validItem(param, form)
					if msg != "" {
						return
					}
					msg, id = cdt.validEnum(param, form)
				}
				return
			}
		case SliceBool:
			setter = func(begin uintptr, form multipart.Form) (msg, id string, err error) {
				// TODO: 同上
				values := form.Value[param]
				if len(values) == 1 {
					vs := strings.Split(values[0], ",")
					values = vs
				}
				var bools = make([]bool, len(values))
				for idx, value := range values {
					bools[idx] = value != ""
				}
				*(*[]bool)(unsafe.Pointer(begin + offset)) = bools
				if cdt != nil {
					msg, id = cdt.validItem(param, form)
					if msg != "" {
						return
					}
					msg, id = cdt.validEnum(param, form)
				}
				return
			}
		default:
		}
		if setter != nil {
			vs.setters = append(vs.setters, setter)
		}
	})
	return vs
}

// 检测是否有文件(FileHandler)字段, 通常有文件数据与无文件数据的数据组织方式是不一样的
func (vs *Parser) HasFileField() bool {
	return len(vs.storages) > 0
}

// 验证并解析数据, 该方法直接映射内存, 是不安全的, 使用时一定要保证 pointer 的模型与 NewParser() 传入的模型一致.
// 对于 FileHandler 型字段, 该方法只负责验证数据, 保存文件还需要调用 StoreFile() 方法
func (vs *Parser) Parse(pointer unsafe.Pointer, form multipart.Form) (msg, id string, err error) {
	begin := uintptr(pointer)
	for _, setter := range vs.setters {
		msg, id, err = setter(begin, form)
		if msg != "" || err != nil {
			return
		}
	}
	return
}

// 保存文件, 自动调用 FileHandler 类型字段所定义的函数, 如果函数未定义, 则返回错误并停止执行
func (vs *Parser) StoreFile(pointer unsafe.Pointer, form multipart.Form) (err error) {
	begin := uintptr(pointer)
	for _, storage := range vs.storages {
		err = storage(begin, form)
		if err != nil {
			return
		}
	}
	return
}

// 20 <= x
var rightGt = regexp.MustCompile(`(-?\d+\.?\d*)(<[=]*)[a-zA-Z]+`)

// x > 20
var leftGt = regexp.MustCompile(`[a-zA-Z]+(>[=]*)(-?\d+\.?\d*)`)

// 20 >= x
var rightLt = regexp.MustCompile(`(-?\d+\.?\d*)(>[=]*)[a-zA-Z]+`)

// x > 20
var leftLt = regexp.MustCompile(`[a-zA-Z]+(<[=]*)(-?\d+\.?\d*)`)

func gt(str string) (n float64, eq, match bool, err error) {
	rms := rightGt.FindAllStringSubmatch(str, 1)
	if len(rms) == 1 {
		if len(rms[0]) == 3 {
			i64, e := strconv.ParseFloat(rms[0][1], 64)
			if e != nil {
				err = e
				return
			}
			return i64, rms[0][2] == "<=", true, nil
		}
	}

	lms := leftGt.FindAllStringSubmatch(str, 1)
	if len(lms) == 1 {
		if len(lms[0]) == 3 {
			i64, e := strconv.ParseFloat(lms[0][2], 64)
			if e != nil {
				err = e
				return
			}
			return i64, lms[0][1] == ">=", true, nil
		}
	}
	return
}

func lt(str string) (n float64, eq, match bool, err error) {
	rms := rightLt.FindAllStringSubmatch(str, 1)
	if len(rms) == 1 {
		if len(rms[0]) == 3 {
			i64, e := strconv.ParseFloat(rms[0][1], 64)
			if e != nil {
				err = e
				return
			}
			return i64, rms[0][2] == ">=", true, nil
		}
	}

	lms := leftLt.FindAllStringSubmatch(str, 1)
	if len(lms) == 1 {
		if len(lms[0]) == 3 {
			i64, e := strconv.ParseFloat(lms[0][2], 64)
			if e != nil {
				err = e
				return
			}
			return i64, lms[0][1] == "<=", true, nil
		}
	}
	return
}

func readArea(str string) (min, max *float64, eqmin, eqmax bool, err error) {
	str = strings.Replace(str, " ", "", -1)
	if n, eq, ok, e := gt(str); e == nil {
		if ok {
			min = &n
			eqmin = eq
		}
	} else {
		err = e
		return
	}
	if n, eq, ok, e := lt(str); e == nil {
		if ok {
			max = &n
			eqmax = eq
		}
	} else {
		err = e
		return
	}
	return
}

func readLength(param string) (min, max int, err error) {
	amin, amax := getBetween(param)
	if amin != nil {
		return *amin, *amax, nil
	}
	_min, _max, eqmin, eqmax, e := readArea(param)
	if e != nil {
		err = e
		return
	}
	if _max == nil {
		err = errors.New("unset max length")
		return
	} else {
		max = int(*_max)
		if !eqmax {
			max -= 1
		}
	}
	if _min == nil || *_min < 0 {
		min = 0
	} else {
		min = int(*_min)
		if !eqmin {
			min += min
		}
	}
	return
}

func MsgName(op string) string {
	return op + "-msg"
}

// match: "6-12"
var between = regexp.MustCompile("^([\\d]+)-([\\d]+)$")

func getBetween(str string) (min, max *int) {
	str = strings.Replace(str, " ", "", -1)
	data := between.FindAllStringSubmatch(str, -1)
	if len(data) == 1 && len(data[0]) == 3 {
		imin, _ := strconv.Atoi(data[0][1])
		imax, _ := strconv.Atoi(data[0][2])
		if imin > imax {
			imin, imax = imax, imin
		}
		min = &imin
		max = &imax
	}
	return
}
